package net.apexes.commons.guice.tx;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author hedyn
 */
class TxnLocalBean {

    private final String txnName;
    private final Isolation isolation;
    private final boolean readonly;
    private final List<Runnable> afterCommitList = new ArrayList<>();
    private final List<Runnable> afterRollbackList = new ArrayList<>();
    private TxDataSource dataSource;
    private TxConnection connection;

    TxnLocalBean(String txnName, Isolation isolation, boolean readonly) {
        this.txnName = txnName;
        this.isolation = isolation;
        this.readonly = readonly;
    }

    void addAfterCommit(Runnable runnable) {
        afterCommitList.add(runnable);
    }

    void addAfterRollback(Runnable runnable) {
        afterRollbackList.add(runnable);
    }

    TxConnection getConnection(TxDataSource ds) throws SQLException {
        if (this.dataSource == null) {
            this.dataSource = ds;
            this.connection = dataSource.createTxConnection();
            this.connection.setReadOnly(readonly);
            configureIsolation(connection, isolation);
        }
        return connection;
    }

    TxConnection getConnection(TxDataSource ds, String username, String password) throws SQLException {
        if (this.dataSource == null) {
            this.dataSource = ds;
            this.connection = dataSource.createTxConnection(username, password);
            this.connection.setReadOnly(readonly);
            configureIsolation(connection, isolation);
        }
        return connection;
    }

    boolean validate(TxDataSource ds) throws SQLException {
        if (TxDataSource.DEFAULT_NAME.equals(txnName)) {
            if (dataSource == null) {
                return true;
            }
            if (dataSource != ds) {
                // 在一个事务中有使用其他数据源时，Tx注解必须指定数据源名称
                throw new SQLException("the @Tx value must be set in a transaction if multiple data sources.");
            }
            return true;
        }
        // Tx注解上设置了名称，只有当即将要获取连接的数据源名称与注解名称一致时该数据源才是有事务的
        return Objects.equals(txnName, ds.getName());
    }

    void commit() throws SQLException {
        if (connection != null) {
            connection.commit();
        }
        for (Runnable runnable : afterCommitList) {
            runnable.run();
        }
    }

    void rollback() throws SQLException {
        if (connection != null) {
            connection.rollback();
        }
        for (Runnable runnable : afterRollbackList) {
            runnable.run();
        }
    }

    void close() throws SQLException {
        TxConnection tmp = connection;
        this.connection = null;
        this.dataSource = null;
        if (tmp != null) {
            tmp.reallyClose();
        }
    }

    private static void configureIsolation(TxConnection conn, Isolation isolation) throws SQLException {
        switch (isolation) {
            case READ_UNCOMMITTED:
                conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
                break;
            case READ_COMMITTED:
                conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                break;
            case REPEATABLE_READ:
                conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
                break;
            case SERIALIZABLE:
                conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
                break;
            default:
                break;
        }
    }
}
